﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;

namespace LongNet.Config
{
    /// <summary>
    /// The factory class helps to creates and fetchs the config file manager for the specified config type. 
    /// </summary>
    /// <remarks>
    /// <example>
    /// <code>
    /// <![CDATA[
    /// [Serializable] // must
    /// public class MyConfigEntity : AppConfigEntity // must
    /// {
    ///    public string AppName { get; set; }
    /// }
    /// 
    /// public static class MyConfigManager
    /// {
    ///    // cache the config managers
    ///    public static string MyConfigPath = @"C:\\my.config";
    ///    public static IFileConfigManager<MyConfigEntity> MyConfig =  = FileConfigManager.CreateManager(MonitorType.Watcher, MyConfigPath, new MyConfigEntity{ AppName = "if the config file is not exist, i will be saved as defult." });
    ///    // ... other configs
    /// }
    /// 
    /// // test
    /// static void Main(string[] args)
    /// {
    ///    // events
    ///    // note: you can put the events in the static method of class ConfigManager
    ///    ConfigManager.MyConfig.ConfigChanged += new AppConfigChangedEventHandler((sender, e) =>
    ///    {
    ///       Console.WriteLine("oldConfig: " + (e.OldConfig as MyConfigEntity).AppName);
    ///       Console.WriteLine("newConfig: " + (e.NewConfig as MyConfigEntity).AppName);
    ///    });
    ///    ConfigManager.MyConfig.ConfigReloadFailed += new AppConfigReloadFailedEventHandler((sender, e) =>
    ///    {
    ///       Console.WriteLine(e.Exception.Message);
    ///    });
    ///    
    ///    // read config
    ///    Console.WriteLine(ConfigManager.MyConfig.GetConfig().AppName);
    ///    //or Console.WriteLine(FileConfigManager.FetchManager<MyConfigEntity>(ConfigManager.MyConfigPath).GetConfig().AppName);
    ///    
    ///    // save config
    ///    var config = ConfigManager.MyConfig.GetConfig();
    ///    config.AppName = Console.ReadLine();
    ///    ConfigManager.MyConfig.SaveConfig(config);
    ///    //or FileConfigManager.FetchManager<MyConfigEntity>(ConfigManager.MyConfigPath).SaveConfig(config);
    ///    
    ///    Console.WriteLine(ConfigManager.MyConfig.GetConfig().AppName);
    /// }
    /// ]]>
    /// </code>
    /// </example>
    /// </remarks>
    public static class FileConfigManager
    {
        #region Private Fields

        private static Hashtable cachedManagers = Hashtable.Synchronized(new Hashtable());

        #endregion

        #region Public Static Methods

        /// <summary>
        /// Checks if manager instance of a given config path exists.
        /// </summary>
        /// <param name="configPath">The config path of the instance.</param>
        /// <returns>true if the manager instance exists, otherwise false.</returns>
        public static bool ManagerExists(string configPath)
        {
            return cachedManagers.ContainsKey(configPath);
        }

        /// <summary>
        /// Fetchs the config file manager for the specified config path. 
        /// The manager must be created first or an exception will be thrown.
        /// </summary>
        /// <typeparam name="T">
        /// The config type of which to be managed by the config file manager be fetched.
        /// The type must be serializable(using the <see cref="System.SerializableAttribute"/>).
        /// </typeparam>
        /// <param name="configPath">The full path of the config file.</param>
        /// <returns>The <see cref="T:IFileConfigManager`1"/> interface.</returns>
        /// <exception cref="AppConfigException">The manager must be created first.</exception>
        public static IFileConfigManager<T> FetchManager<T>(string configPath)
            where T : FileConfigEntity, new()
        {
            //Type type = typeof(T);
            //int hash = type.GetHashCode();

            string key = configPath.ToLower();

            IFileConfigManager<T> manager = cachedManagers[key] as IFileConfigManager<T>;

            if (manager == null)
                throw new AppConfigException(string.Format("Fail to fetch. The config manager for {0}{1} has not been created. You should call FileConfigManager.CreateManager<{0}>(params) first.", typeof(T), configPath));


            return manager;
        }

        /// <summary>
        /// Creates a config file manager for the specified config type.
        /// The manager of each specified config type only can be created once or an exception will be thrown.
        /// You can cache the created manager or call  method Fetch to get the manager auto-cached by the factory.
        /// </summary>
        /// <typeparam name="T">
        /// The config type of which to be managed by the config file manager be created.
        /// The type must be serializable(using the <see cref="System.SerializableAttribute"/>).
        /// </typeparam>
        /// <param name="monitor">The monitor type used to reload config file if it gets changed.</param>
        /// <param name="configPath">The full path of the config file.</param>
        /// <returns>The <see cref="T:IFileConfigManager`1"/> interface.</returns>
        /// <exception cref="AppConfigException">The manager of each specified config type only can be created once.</exception>
        public static IFileConfigManager<T> CreateManager<T>(ConfigMonitorType monitor, string configPath)
            where T : FileConfigEntity, new()
        {
            return CreateManager<T>(monitor, configPath, new T());
        }

        /// <summary>
        /// Creates a config file manager for the specified config type.
        /// The manager of each specified config type only can be created once or an exception will be thrown.
        /// You can cache the created manager or call  method Fetch to get the manager auto-cached by the factory.
        /// </summary>
        /// <typeparam name="T">
        /// The config type of which to be managed by the config file manager be created.
        /// The type must be serializable(using the <see cref="System.SerializableAttribute"/>).
        /// </typeparam>
        /// <param name="monitor">The monitor type used to reload config file if it gets changed.</param>
        /// <param name="configPath">The full path of the config file.</param>
        /// <param name="fileType">The config file type when saving file.</param>
        /// <returns>The <see cref="T:IFileConfigManager`1"/> interface.</returns>
        /// <exception cref="AppConfigException">Only one manager of each specified config type can be created.</exception>
        public static IFileConfigManager<T> CreateManager<T>(ConfigMonitorType monitor, string configPath, ConfigFileType fileType)
                    where T : FileConfigEntity, new()
        {
            return CreateManager<T>(monitor, configPath, new T(), fileType);
        }

        /// <summary>
        /// Creates a config file manager for the specified config type.
        /// Only one manager of each specified config path can be created or an exception will be thrown.
        /// You can cache the created manager or call  method Fetch to get the manager auto-cached by the factory.
        /// </summary>
        /// <typeparam name="T">
        /// The config type of which to be managed by the config file manager be created.
        /// The type must be serializable(using the <see cref="System.SerializableAttribute"/>).
        /// </typeparam>
        /// <param name="monitor">The monitor type used to reload config file if it gets changed.</param>
        /// <param name="configPath">The full path of the config file.</param>
        /// <param name="configEntry">
        /// The config entity. If the config file is not exists this config entity will be saved to file.
        /// </param>
        /// <returns>The <see cref="T:IFileConfigManager`1"/> interface.</returns>
        /// <exception cref="AppConfigException">Only one manager of each specified config type can be created.</exception>
        public static IFileConfigManager<T> CreateManager<T>(ConfigMonitorType monitor, string configPath, T configEntry)
                    where T : FileConfigEntity, new()
        {
            return CreateManager<T>(monitor, configPath, configEntry, ConfigFileType.Xml);
        }

        /// <summary>
        /// Creates a config file manager for the specified config type.
        /// Only one manager of each specified config path can be created or an exception will be thrown.
        /// You can cache the created manager or call  method Fetch to get the manager auto-cached by the factory.
        /// </summary>
        /// <typeparam name="T">
        /// The config type of which to be managed by the config file manager be created.
        /// The type must be serializable(using the <see cref="System.SerializableAttribute"/>).
        /// </typeparam>
        /// <param name="monitor">The monitor type used to reload config file if it gets changed.</param>
        /// <param name="configPath">The full path of the config file.</param>
        /// <param name="configEntry">
        /// The config entity. If the config file is not exists this config entity will be saved to file.
        /// </param>
        /// <param name="fileType">The config file type when saving file.</param>
        /// <returns>The <see cref="T:IFileConfigManager`1"/> interface.</returns>
        /// <exception cref="AppConfigException">Only one manager of each specified config type can be created.</exception>
        public static IFileConfigManager<T> CreateManager<T>(ConfigMonitorType monitor, string configPath, T configEntry, ConfigFileType fileType)
            where T : FileConfigEntity, new()
        {
            //Type type = typeof(T);
            //int hash = type.GetHashCode();
            //object manager = cachedManagers[hash];

            string key = configPath.ToLower();
            object manager = cachedManagers[key];

            if (manager == null)
            {
                switch (monitor)
                {
                    case ConfigMonitorType.None:
                        manager = new FileConfigManagerUsingTimer<T>(configPath, configEntry, fileType, false);
                        break;
                    case ConfigMonitorType.Timer:
                        manager = new FileConfigManagerUsingTimer<T>(configPath, configEntry, fileType, true);
                        break;
                    case ConfigMonitorType.Watcher:
                        manager = new FileConfigManagerUsingWatcher<T>(configPath, configEntry, fileType);
                        break;
                    default:
                        throw new NotSupportedException("Not supported MonitorType.");
                }
                cachedManagers[key] = manager;

                return manager as IFileConfigManager<T>;
            }
            else
            {
                throw new AppConfigException(string.Format("After the config manager for {0}({1}) has been created, you should call FileConfigManager.FetchManager<{0}>() to get the manager. Or you can cache it yourself for convenience.", typeof(T), configPath));
            }
        }

        #endregion
    }
}
